package net.paoding.rose.jade.statement.jexl.internal.introspection;

import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.internal.introspection.BooleanGetExecutor;
import org.apache.commons.jexl3.internal.introspection.DuckGetExecutor;
import org.apache.commons.jexl3.internal.introspection.FieldGetExecutor;
import org.apache.commons.jexl3.internal.introspection.IndexedType;
import org.apache.commons.jexl3.internal.introspection.Introspector;
import org.apache.commons.jexl3.internal.introspection.ListGetExecutor;
import org.apache.commons.jexl3.internal.introspection.MapGetExecutor;
import org.apache.commons.jexl3.internal.introspection.PropertyGetExecutor;
import org.apache.commons.jexl3.internal.introspection.Uberspect;
import org.apache.commons.jexl3.introspection.JexlPermissions;
import org.apache.commons.jexl3.introspection.JexlPropertyGet;
import org.apache.commons.jexl3.introspection.JexlUberspect;
import org.apache.commons.lang3.reflect.FieldUtils;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;

/**
 * 重写{@link Uberspect#getPropertyGet(List, Object, Object)}方法,允许getDeclaredField
 * @author zhangfusheng
 * @date 2024/3/22
 */
public class RoseUberspect extends Uberspect {

    public RoseUberspect(JexlPermissions permissions) {
        super(null, JexlUberspect.JEXL_STRATEGY, permissions);
    }

    @Override
    public JexlPropertyGet getPropertyGet(List<PropertyResolver> resolvers, Object obj, Object identifier) {
        final Class<?> clazz = obj.getClass();
        final String property = RoseUberspect.castString(identifier);
        final Introspector is = base();
        final List<PropertyResolver> r = resolvers == null ? JexlUberspect.JEXL_STRATEGY.apply(null, obj) : resolvers;
        JexlPropertyGet executor = null;
        for (final PropertyResolver resolver : r) {
            if (resolver instanceof JexlResolver) {
                switch ((JexlResolver) resolver) {
                    case PROPERTY:
                        // first try for a getFoo() type of property (also getfoo() )
                        executor = PropertyGetExecutor.discover(is, clazz, property);
                        if (executor == null) {
                            executor = BooleanGetExecutor.discover(is, clazz, property);
                        }
                        break;
                    case MAP:
                        // let's see if we are a map...
                        executor = MapGetExecutor.discover(is, clazz, identifier);
                        break;
                    case LIST:
                        // let's see if this is a list or array
                        final Integer index = RoseUberspect.castInteger(identifier);
                        if (index != null) {
                            executor = ListGetExecutor.discover(is, clazz, index);
                        }
                        break;
                    case DUCK:
                        // if that didn't work, look for get(foo)
                        executor = DuckGetExecutor.discover(is, clazz, identifier);
                        if (executor == null && property != null && property != identifier) {
                            // look for get("foo") if we did not try yet (just above)
                            executor = DuckGetExecutor.discover(is, clazz, property);
                        }
                        break;
                    case FIELD:
                        // a field may be? (can not be a number)
                        executor = FieldGetExecutor.discover(is, clazz, property);
                        // static class fields (enums included)
                        if (obj instanceof Class<?>) {
                            executor = FieldGetExecutor.discover(is, (Class<?>) obj, property);
                        }
                        if (Objects.isNull(executor)) {
                            // a field may be? (can not be a number)
                            executor = RoseUberspect.fieldGetExecutorDiscover(is, clazz, property);
                            // static class fields (enums included)
                            if (obj instanceof Class<?>) {
                                executor = RoseUberspect.fieldGetExecutorDiscover(is, (Class<?>) obj, property);
                            }
                        }
                        break;
                    case CONTAINER:
                        // or an indexed property?
                        executor = IndexedType.discover(is, obj, property);
                        break;
                    default:
                        continue; // in case we add new ones in enum
                }
            } else {
                executor = resolver.getPropertyGet(this, obj, identifier);
            }
            if (executor != null) return executor;
        }
        return null;
    }

    static String castString(final Object arg) {
        return arg instanceof CharSequence || arg instanceof Integer ? arg.toString() : null;
    }

    static Integer castInteger(final Object arg) {
        return arg instanceof Number ? ((Number) arg).intValue() : null;
    }

    static JexlPropertyGet fieldGetExecutorDiscover(Introspector is, Class<?> clazz, String identifier) {
        if (Objects.isNull(identifier)) return null;
        final Field field1 = is.getField(clazz, identifier);
        final Field field = Objects.nonNull(field1) ? field1 : FieldUtils.getDeclaredField(clazz, identifier, true);
        if (Objects.isNull(field)) return null;
        return new JexlPropertyGet() {
            @Override
            public Object invoke(Object obj) throws Exception {
                return field.get(obj);
            }

            @Override
            public Object tryInvoke(Object obj, Object key) throws JexlException.TryFailed {
                if (obj.getClass().equals(field.getDeclaringClass()) && key.equals(field.getName())) {
                    try {
                        return field.get(obj);
                    } catch (final IllegalAccessException xill) {
                        return Uberspect.TRY_FAILED;
                    }
                }
                return Uberspect.TRY_FAILED;
            }

            @Override
            public boolean tryFailed(Object rval) {
                return rval == Uberspect.TRY_FAILED;
            }

            @Override
            public boolean isCacheable() {
                return true;
            }
        };
    }
}
